Skip to content

feat(wasm): Complete WASM auth plugins support - #1938

Merged
gtema merged 1 commit into
mainfrom
claude/extism-plugin-registry-d0p7e3
Aug 12, 2026
Merged

feat(wasm): Complete WASM auth plugins support#1938
gtema merged 1 commit into
mainfrom
claude/extism-plugin-registry-d0p7e3

Conversation

@gtema

@gtema gtema commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Adds osc plugin search/install/update against an HTTPS-hosted registry index (bootstrapped at plugins/registry/index.json in this repo), with sha256 verification before any bytes touch disk.

Before a registry-installed plugin is trusted, verify it was actually published by CI in its claimed GitHub repo: fetch the GitHub artifact attestation, verify the DSSE envelope signature against the Fulcio leaf cert, chain-verify against vendored pinned Fulcio root/intermediate CAs, and check the cert's OIDC-issuer/repository identity extensions match the plugin's declared source_repo. Rekor transparency-log inclusion is recorded for display but not cryptographically verified (documented, deliberate scope limit - see provenance.rs module docs).

An unverified or tampered plugin fails closed (Untrusted) unless the caller passes --allow-unsigned, which is loudly logged and surfaced by osc plugin list. Local --file installs now go through the same explicit escape hatch, since a local file has no attestation to check. osc plugin update re-verifies provenance on every run rather than trusting a stored record from first install.

Move the browser-based SSO callback server (bind/listen, HTML response, browser opening) out of sdk/auth-websso into a new sdk/websso-host crate, so both the native WebSSO plugin and the upcoming WASM SSO ABI (Phase 5) can share one implementation of this security-sensitive logic instead of duplicating it.

The shared CallbackServer now embeds a host-generated, per-flow random state token in the callback URL's query string and rejects any callback whose state doesn't match via a constant-time comparison, closing a CSRF gap the previous implementation didn't have. The browser-open path is gated by a BrowserOpenPolicy so callers can choose whether to allow plain http:// (kept for the native plugin's existing local/dev Keystone use case) or require https://.

sdk/auth-websso is trimmed down to a thin caller of the new crate; its local hyper server, HTML template and tests move to sdk/websso-host along with their coverage.

Extends osc plugin's WASM auth ABI with a second, mutually-exclusive flavor for browser-based SSO logins: sso_build_request (pure, guest computes an https:// URL to open and declares its intended redirect host) and sso_parse_callback (pure, guest turns an already CSRF-validated callback into a token). Neither export gets a socket or browser-opening capability — both are structurally guest-sandboxed (Manifest::disallow_all_hosts) — so all I/O (the local callback listener, anti-CSRF state check, and browser launch) stays in the host, reusing the openstack-sdk-websso-host service extracted earlier.

WasmAuthPlugin::load detects which flavor a module implements via Plugin::function_exists and rejects modules exporting both or neither. Before ever prompting the user or opening a browser, auth_via_sso hard-rejects a non-https url and any redirect_host that doesn't exactly match the host-bound callback listener's own authority — both checks are unconditional, with no --allow-unsigned- style override, since a mismatch here means the plugin is trying to redirect the callback somewhere the host didn't intend.

Adds an example SSO WASM plugin fixture (fixtures/example-sso-plugin, checked in as tests/fixtures/example_sso.wasm) and an integration test suite exercising ABI-flavor detection, the two hard-fail security checks (proven to trigger before the interactive confirmation step), the guest ABI's request/callback round trip, and the shared callback server's anti-CSRF rejection.

Adds three libFuzzer targets covering the untrusted-input surfaces where a WASM auth plugin's own bytes cross into host code, extending the existing fuzz/ crate rather than starting a new one:

  • fuzz_wasm_plugin_identity_http_request: the one host function every plugin can call, fuzzing its request-parsing/validation step (JSON decode, relative-path check, URL join, method parse) ahead of any network I/O.
  • fuzz_wasm_plugin_sso_build_response: the SSO ABI flavor's security-relevant validation (URL must parse and be https, redirect_host must match the host-bound callback listener) that runs before a browser is ever opened.
  • fuzz_wasm_plugin_auth_result: the AuthResultMsg deserialization every guest response (auth, sso_parse_callback) is parsed through.

Each target exercises the crate's real (previously inline, now extracted into standalone functions: host::resolve_request and plugin::validate_sso_build_response) parsing/validation logic directly, via new fuzzing-feature-gated entry points, rather than reimplementing it -- matching this workspace's existing fuzz-target conventions (openstack-sdk-auth-core, openstack_sdk_core). Wired into the fuzz CI job alongside the existing targets.

Documents the osc plugin feature end to end: the operator-facing guide (sandbox model, installing, trust model), the plugin author's guest ABI reference (both auth and sso flavors, host-mediated HTTP capability, build/test/publish flow), and how entries land in and are trusted out of the plugins/registry index. Wires all three pages into the mdBook table of contents.

cargo deny check already passes cleanly against the extism/wasmtime and sigstore-adjacent (x509-parser/ring/rcgen) dependency tree added in earlier phases, so no deny.toml changes were needed for Phase 6.

Assisted-By: Claude Sonnet 5 noreply@anthropic.com

Adds osc plugin search/install/update against an HTTPS-hosted registry
index (bootstrapped at plugins/registry/index.json in this repo), with
sha256 verification before any bytes touch disk.

Before a registry-installed plugin is trusted, verify it was actually
published by CI in its claimed GitHub repo: fetch the GitHub artifact
attestation, verify the DSSE envelope signature against the Fulcio leaf
cert, chain-verify against vendored pinned Fulcio root/intermediate CAs,
and check the cert's OIDC-issuer/repository identity extensions match
the plugin's declared source_repo. Rekor transparency-log inclusion is
recorded for display but not cryptographically verified (documented,
deliberate scope limit - see provenance.rs module docs).

An unverified or tampered plugin fails closed (Untrusted) unless the
caller passes --allow-unsigned, which is loudly logged and surfaced by
`osc plugin list`. Local --file installs now go through the same
explicit escape hatch, since a local file has no attestation to check.
`osc plugin update` re-verifies provenance on every run rather than
trusting a stored record from first install.

Move the browser-based SSO callback server (bind/listen, HTML response,
browser opening) out of sdk/auth-websso into a new sdk/websso-host
crate, so both the native WebSSO plugin and the upcoming WASM SSO ABI
(Phase 5) can share one implementation of this security-sensitive logic
instead of duplicating it.

The shared CallbackServer now embeds a host-generated, per-flow random
state token in the callback URL's query string and rejects any callback
whose state doesn't match via a constant-time comparison, closing a CSRF
gap the previous implementation didn't have. The browser-open path is
gated by a BrowserOpenPolicy so callers can choose whether to allow
plain http:// (kept for the native plugin's existing local/dev Keystone
use case) or require https://.

sdk/auth-websso is trimmed down to a thin caller of the new crate; its
local hyper server, HTML template and tests move to sdk/websso-host
along with their coverage.

Extends osc plugin's WASM auth ABI with a second, mutually-exclusive
flavor for browser-based SSO logins: `sso_build_request` (pure, guest
computes an https:// URL to open and declares its intended redirect
host) and `sso_parse_callback` (pure, guest turns an already
CSRF-validated callback into a token). Neither export gets a socket or
browser-opening capability — both are structurally guest-sandboxed
(`Manifest::disallow_all_hosts`) — so all I/O (the local callback
listener, anti-CSRF state check, and browser launch) stays in the host,
reusing the `openstack-sdk-websso-host` service extracted earlier.

`WasmAuthPlugin::load` detects which flavor a module implements via
`Plugin::function_exists` and rejects modules exporting both or neither.
Before ever prompting the user or opening a browser, `auth_via_sso`
hard-rejects a non-https `url` and any `redirect_host` that doesn't
exactly match the host-bound callback listener's own authority — both
checks are unconditional, with no `--allow-unsigned`- style override,
since a mismatch here means the plugin is trying to redirect the
callback somewhere the host didn't intend.

Adds an example SSO WASM plugin fixture (fixtures/example-sso-plugin,
checked in as tests/fixtures/example_sso.wasm) and an integration test
suite exercising ABI-flavor detection, the two hard-fail security checks
(proven to trigger before the interactive confirmation step), the guest
ABI's request/callback round trip, and the shared callback server's
anti-CSRF rejection.

Adds three libFuzzer targets covering the untrusted-input surfaces where
a WASM auth plugin's own bytes cross into host code, extending the
existing fuzz/ crate rather than starting a new one:

- fuzz_wasm_plugin_identity_http_request: the one host function every
  plugin can call, fuzzing its request-parsing/validation step (JSON
decode, relative-path check, URL join, method parse) ahead of any
network I/O.
- fuzz_wasm_plugin_sso_build_response: the SSO ABI flavor's
  security-relevant validation (URL must parse and be https,
redirect_host must match the host-bound callback listener) that runs
before a browser is ever opened.
- fuzz_wasm_plugin_auth_result: the AuthResultMsg deserialization every
  guest response (`auth`, `sso_parse_callback`) is parsed through.

Each target exercises the crate's real (previously inline, now extracted
into standalone functions: `host::resolve_request` and
`plugin::validate_sso_build_response`) parsing/validation logic
directly, via new `fuzzing`-feature-gated entry points, rather than
reimplementing it -- matching this workspace's existing fuzz-target
conventions (openstack-sdk-auth-core, openstack_sdk_core). Wired into
the `fuzz` CI job alongside the existing targets.

Documents the osc plugin feature end to end: the operator-facing guide
(sandbox model, installing, trust model), the plugin author's guest ABI
reference (both auth and sso flavors, host-mediated HTTP capability,
build/test/publish flow), and how entries land in and are trusted out of
the plugins/registry index. Wires all three pages into the mdBook table
of contents.

cargo deny check already passes cleanly against the extism/wasmtime and
sigstore-adjacent (x509-parser/ring/rcgen) dependency tree added in
earlier phases, so no deny.toml changes were needed for Phase 6.

Assisted-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Artem Goncharov <artem.goncharov@gmail.com>
@gtema
gtema force-pushed the claude/extism-plugin-registry-d0p7e3 branch from 56c63cc to f85ee8d Compare August 12, 2026 08:11
@gtema
gtema merged commit fc0d3d8 into main Aug 12, 2026
24 checks passed
@gtema
gtema deleted the claude/extism-plugin-registry-d0p7e3 branch August 12, 2026 09:09
@gtema-release-plz gtema-release-plz Bot mentioned this pull request Aug 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant